﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Serialport串口工具
{
    public partial class Form1 : Form
    {

        SerialPort _sPort = null;
        public static string[] BaudRates = new string[] {
            "110",
            "300",
            "600",
            "1200",
            "2400",
            "4800",
            "9600",
            "14400",
            "19200",
            "38400",
            "56000",
            "57600",
            "115200",
            "128000",
            "256000",
            "460800",
            "500000",
            "512000",
            "600000",
            "750000",
            "921600",
            "1000000",
            "1500000",
            "2000000"
        };

        /// <summary>
        /// 接收线程
        /// </summary>
        Thread recvTh = null;
        /// <summary>
        /// 启动数据线程
        /// </summary>
        Thread startDataTh = null;
        /// <summary>
        /// 超时线程
        /// </summary>
        Thread timeoutTh = null;

        /// <summary>
        /// 停止自动发送
        /// </summary>
        bool stopAutoSendFlag = false;
        /// <summary>
        /// 接收时间
        /// </summary>
        DateTime recvDateTime = DateTime.Now;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            InitCmbPortName();
            InitCmbBaudRate();
            InitCmbDataBit();
            InitCmbStopBit();
            InitCmbParityBit();
        }

        /// <summary>
        /// 发送启动数据
        /// </summary>
        private void SendStartDataThread() {

            int count = 0;

            while (true) {

                Thread.Sleep(100);
                try {

                    if (_sPort == null) {
                        count = 0;
                        continue;
                    }

                    if (_sPort.IsOpen == false) {
                        count = 0;
                        continue;
                    }

                    if (stopAutoSendFlag) {
                        count = 0;
                        continue;
                    }

                    if (count == 8) {
                        count = 0;
                        byte[] bytes = new byte[] { 0x01, 0x00 };
                        SendData(bytes);
                        //_sPort.Write(bytes, 0, bytes.Length);
                    }

                    count++;
                } catch (Exception e) {
                    ShowMsg("程序异常:" + e.Message.ToString());
                }

            }
        }

        private void ShowMsg(string msg) {
            this.Invoke(new EventHandler(delegate {
                tbMsg.Text += msg + "\r\n";
            }));

            this.Invoke(new EventHandler(delegate
            {
                string text = tbMsg.Text;
                if (text.Length > 1000)
                {
                    text = text.Substring(text.Length - 1000, 1000);
                }
                //===========================
                tbMsg.SelectionStart = tbMsg.Text.Length;//接收
                tbMsg.ScrollToCaret();
            }));
        }

        private void btnFlush_Click(object sender, EventArgs e)
        {
            InitCmbPortName();
        }

        private void InitCmbPortName()
        {
            //===========================================================================
            if (cmbPortName.Items.Count > 0) cmbPortName.Items.Clear();
            string[] portNames = SerialPort.GetPortNames();
            foreach (var name in portNames) cmbPortName.Items.Add(name);
            if (cmbPortName.Items.Count > 0) cmbPortName.SelectedIndex = 0;
        }

        private void InitCmbBaudRate()
        {
            if (cmbBaudRate.Items.Count > 0) cmbBaudRate.Items.Clear();
            foreach (var br in BaudRates) cmbBaudRate.Items.Add(br);
            if (cmbBaudRate.Items.Count > 0) cmbBaudRate.SelectedIndex = 6;
        }

        private void InitCmbDataBit()
        {
            string[] dataBits = new string[] { "5", "6", "7", "8" };
            if (cmbDataBit.Items.Count > 0) cmbDataBit.Items.Clear();
            foreach (var bit in dataBits) cmbDataBit.Items.Add(bit);

            if (cmbDataBit.Items.Count > 0) cmbDataBit.SelectedIndex = cmbDataBit.Items.Count - 1;
        }

        private void InitCmbStopBit()
        {
            if (cmbStopBit.Items.Count > 0) cmbStopBit.Items.Clear();

            cmbStopBit.Items.Add(StopBits.None.ToString());
            cmbStopBit.Items.Add(StopBits.One.ToString());
            cmbStopBit.Items.Add(StopBits.Two.ToString());
            cmbStopBit.Items.Add(StopBits.OnePointFive.ToString());

            if (cmbStopBit.Items.Count > 0) cmbStopBit.SelectedIndex = 1;
        }

        private void InitCmbParityBit()
        {
            if (cmbParityBit.Items.Count > 0) cmbParityBit.Items.Clear();

            cmbParityBit.Items.Add(Parity.None.ToString());
            cmbParityBit.Items.Add(Parity.Odd.ToString());
            cmbParityBit.Items.Add(Parity.Even.ToString());
            cmbParityBit.Items.Add(Parity.Mark.ToString());
            cmbParityBit.Items.Add(Parity.Space.ToString());

            if (cmbParityBit.Items.Count > 0) cmbParityBit.SelectedIndex = 1;
        }

        public void SetSerialPortParam(string portName, int portBaud, int dataBit, StopBits stopBit, Parity parity)
        {
            this._sPort = new SerialPort(portName, portBaud, parity, dataBit, stopBit);
            this._sPort.ReadBufferSize = 1024 * 1024;
        }

        private void btnCtl_Click(object sender, EventArgs e)
        {
            if (_sPort == null || _sPort.IsOpen == false) {
                this.SetSerialPortParam(cmbPortName.Text.Trim(),
                    Convert.ToInt32(cmbBaudRate.Text.Trim()),
                    Convert.ToInt32(cmbDataBit.Text.Trim()),
                    GetStopBits(cmbStopBit.Text.Trim()),
                    GetParity(cmbParityBit.Text.Trim())
                    );
                _sPort.Open();
                btnCtl.Text = "关闭串口";
                StartRecvThread();
                StartStartDataThread();
                StartTimeoutThread();
                ShowMsg("打开串口成功!");
                

            } else {

                StopStartDataThread();
                StopRecvThread();
                StopTimeoutThread();
                _sPort.Close();
                _sPort = null;
                btnCtl.Text = "打开串口";
                ShowMsg("串口已关闭!");

            }
        }


        private void StartRecvThread() {
            if (recvTh != null)
                return;
            recvTh = new Thread(RecvThread);
            recvTh.IsBackground = true;
            recvTh.Start();
        }

        private void StopRecvThread() {
            if (recvTh == null)
                return;
            recvTh.Abort();
            recvTh = null;
        }

        private void StartStartDataThread() {
            if (startDataTh != null)
                return;
            startDataTh = new Thread(SendStartDataThread);
            startDataTh.IsBackground = true;
            startDataTh.Start();
        }

        private void StopStartDataThread() {
            if (startDataTh == null)
                return;
            startDataTh.Abort();
            startDataTh = null;
        }

        private void StartTimeoutThread() {
            if (timeoutTh != null)
                return;
            timeoutTh = new Thread(TimeoutThread);
            timeoutTh.IsBackground = true;
            timeoutTh.Start();
        }

        private void StopTimeoutThread() {
            if (timeoutTh == null)
                return;
            timeoutTh.Abort();
            timeoutTh = null;
        }

        private void TimeoutThread() {
            while (true) {
                Thread.Sleep(100);
                if (stopAutoSendFlag == false)
                    continue;
                TimeSpan ts = DateTime.Now - recvDateTime;
                if (ts.TotalMilliseconds < 3000)
                    continue;
                stopAutoSendFlag = false;
            }
        }

        private void RecvThread() {

            int oldRecNum = 0;
            int curRecNum = 0;
            int timerout = 0;

            while (true) {
                try {
                    #region 串口接收部分
                    double recvTime = 1000 * 10 * 5 / this._sPort.BaudRate;
                    if (recvTime < 5) recvTime = 5;

                    try {
                        while (this._sPort.BytesToRead == 0) {
                            Thread.Sleep((int)recvTime);//没有数据，即一直在这里
                        }
                    } catch {
                        Thread.Sleep((int)recvTime);
                        continue;//继续下一个循环
                    }

                    stopAutoSendFlag = true;
                    recvDateTime = DateTime.Now;

                    timerout = 0;
                    DateTime dt = DateTime.Now;
                    TimeSpan ts;
                    do {
                        oldRecNum = curRecNum;
                        curRecNum = this._sPort.BytesToRead;
                        Thread.Sleep((int)recvTime);
                        if (oldRecNum == curRecNum) {
                            timerout++;
                        } else {
                            timerout = 0;
                        }
                        ts = DateTime.Now - dt;
                    } while ((timerout < 2) && (ts.TotalMilliseconds < 500));

                    int BytesToRead = this._sPort.BytesToRead;
                    byte[] _recBuf = new byte[BytesToRead];
                    try {
                        int cnt = this._sPort.Read(_recBuf, 0, BytesToRead);
                    }
                    catch { }
                    
                    #endregion


                    #region 数据处理部分

                    ReceiveMsgShow(_recBuf);

                    #endregion
                }
                catch (Exception e) {
                    ShowMsg("接收异常:" + e.Message.ToString());
                }
            }
        }

        private void ReceiveMsgShow(byte[] bytes) {

            string msg = "";
            foreach (var b in bytes) {
                if (msg != "")
                    msg += " ";

                msg += b.ToString("X2");
            }
            ShowMsg(">>>接收:" + msg);

            if (bytes.Length == 1) {
                if (bytes[0] == 0x01)
                {
                    byte[] bs = new byte[] { 0x0F };
                    SendData(bs);
                }
            } else {
                int len = bytes.Length;
                if(bytes[len-1] == 0xBB)
                {
                    byte[] bs = new byte[] { 0x1F };
                    SendData(bs);
                    stopAutoSendFlag = false;
                }
            }
        }

        private void SendData(byte[] bytes) {
            if (_sPort == null)
                return;
            _sPort.Write(bytes, 0, bytes.Length);
            string msg = "";
            foreach (var b in bytes) {
                if (msg != "")
                    msg += " ";
                msg += b.ToString("X2");
            }
            ShowMsg(">>>发送:" + msg);
        }

        private StopBits GetStopBits(string stopBit)
        {
            if (stopBit == StopBits.None.ToString()) return StopBits.None;
            if (stopBit == StopBits.One.ToString()) return StopBits.One;
            if (stopBit == StopBits.Two.ToString()) return StopBits.Two;
            return StopBits.OnePointFive;
        }


        private Parity GetParity(string parityStr)
        {
            if (parityStr == Parity.None.ToString()) return Parity.None;
            if (parityStr == Parity.Odd.ToString()) return Parity.Odd;
            if (parityStr == Parity.Even.ToString()) return Parity.Even;
            if (parityStr == Parity.Mark.ToString()) return Parity.Mark;
            return Parity.Space;
        }

        private void btnClear_Click(object sender, EventArgs e)
        {
            tbMsg.Clear();
        }
    }
}
